﻿using System;
using System.Data;
using JTLeigh.Commerce.Spencer.Core.Common;
using JTLeigh.Commerce.Spencer.Core.Common.Services;
using JTLeigh.Commerce.Spencer.Core.Data.Services;
using JTLeigh.Commerce.Spencer.Features.ProductImages.Common;
using JTLeigh.Commerce.Spencer.Features.ProductImages.Common.Entities;
using Microsoft.Practices.Unity;

namespace JTLeigh.Commerce.Spencer.Features.ProductImages.Data.Providers
{

	/// <summary>
	/// An <see cref="IProductImageMetadataProvider"/> implementation that retrieves product image 
	/// metadata from the database.
	/// </summary>
	public class ProductImageMetadataProvider : IProductImageMetadataProvider
	{

		#region Inner Classes and Types

		internal static class StoredProcedure
		{

			public static class GetProductImageMetadata
			{

				public const string CommandName = "GetProductImageMetadata";

				public static class ParameterName
				{
					public const string TenantId = "@TenantId";
				}

				public static class ColumnName
				{

					public static class ProductImageSize
					{
						public const string SizeId = "SizeId";
						public const string WellKnownSizeCode = "WellKnownSizeCode";
						public const string Width = "Width";
						public const string Height = "Height";
						public const string DisplayOrder = "DisplayOrder";
						public const string DefaultName = "DefaultName";
						public const string DefaultMissingImagePath = "DefaultMissingImagePath";
					}

					public static class ProductImageSizeLocalization
					{
						public const string CultureId = "CultureId";
						public const string Name = "Name";
						public const string MissingImagePath = "MissingImagePath";
					}

					public static class ProductImageView
					{
						public const string ViewId = "ViewId";
						public const string WellKnownViewCode = "WellKnownViewCode";
						public const string DisplayOrder = "DisplayOrder";
						public const string DefaultName = "DefaultName";
					}

					public static class ProductImageViewLocalization
					{
						public const string CultureId = "CultureId";
						public const string Name = "Name";
					}

				}

			}

		}

		#endregion // Inner Classes and Types

		#region Constants

		/// <summary>
		/// The database key to use to create database connections for performing read operations.
		/// </summary>
		protected internal const string DatabaseKeyReadOperations = "ProductImageMetadataReadOperations";

		/// <summary>
		/// The schema name to qualify commands with.
		/// </summary>
		protected internal const string DatabaseSchema = "ProductImage";

		#endregion // Constants

		#region Properties

		/// <summary>
		/// Gets or sets the database connection factory to use to create connections to databases.
		/// </summary>
		/// <value>An <see cref="IDatabaseConnectionFactory"/> implementation.</value>
		[Dependency]
		public IDatabaseConnectionFactory DatabaseConnectionFactory { get; set; }

		/// <summary>
		/// Gets or sets the exception handling service to use to handle exceptions.
		/// </summary>
		/// <value>An <see cref="IExceptionHandlingService"/> implementation.</value>
		[Dependency]
		public IExceptionHandlingService ExceptionHandlingService { get; set; }

		#endregion // Properties

		#region IProductImageMetadataProvider Members

		/// <summary>
		/// Gets all product image metadata from the database.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant whose
		/// metadata is to be retrieved.</param>
		/// <returns>A <see cref="ProductImageMetadata"/> containing all sizes, views, etc.</returns>
		public ProductImageMetadata GetProductImageMetadata(Guid tenantId)
		{
			IDbCommand command;
			ProductImageMetadata metadata;

			try
			{
				using (IDbConnection connection = CreateDatabaseConnectionReadOperations(tenantId))
				{
					connection.Open();
					try
					{
						using (command = CreateGetProductImageMetadataCommand(connection, tenantId))
						{
							metadata = ReadProductImageMetadata(command);
						}
						return metadata;
					}
					finally
					{
						connection.Close();
					}
				}
			}
			catch (Exception e)
			{
				ExceptionHandlingService.HandleException(tenantId, e, ProductImagesExceptionPolicy.Data);
				throw;
			}
		}

		#endregion // IProductImageMetadataProvider Members

		#region Implementation

		/// <summary>
		/// Creates a connection to the database to be used for performing read operations.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant who the
		/// database connection is being created for.</param>
		/// <returns>An <see cref="IDbConnection"/> connection to the specified database.</returns>
		protected virtual IDbConnection CreateDatabaseConnectionReadOperations(Guid tenantId)
		{
			return DatabaseConnectionFactory.CreateDatabaseConnection(tenantId, DatabaseKeyReadOperations);
		}

		/// <summary>
		/// Gets the fully-qualified command name.
		/// </summary>
		/// <param name="tenantId">The tenant identifier <see cref="Guid"/> of the tenant who the
		/// command name is being qualified for.</param>
		/// <param name="commandName">The command name to qualify.</param>
		/// <returns>The fully-qualified command name.</returns>
		protected virtual string GetFullyQualifiedCommandName(Guid tenantId, string commandName)
		{
			return String.Concat("[", DatabaseSchema, "].[", commandName, "]");
		}

		private IDbCommand CreateGetProductImageMetadataCommand(IDbConnection connection, Guid tenantId)
		{
			IDbCommand command;
			IDbDataParameter parameter;

			Safety.AssertArgumentNotNull("connection", connection);

			command = connection.CreateCommand();
			command.CommandText = GetFullyQualifiedCommandName(tenantId, StoredProcedure.GetProductImageMetadata.CommandName);
			command.CommandType = CommandType.StoredProcedure;

			parameter = command.CreateParameter();
			parameter.ParameterName = StoredProcedure.GetProductImageMetadata.ParameterName.TenantId;
			parameter.Value = tenantId;
			command.Parameters.Add(parameter);

			return command;
		}

		private static ProductImageMetadata ReadProductImageMetadata(IDbCommand command)
		{
			ProductImageMetadata metadata;

			Safety.AssertArgumentNotNull("command", command);

			metadata = new ProductImageMetadata();
			using (IDataReader reader = command.ExecuteReader())
			{
				try
				{
					ReadSizes(reader, metadata);
					Safety.AssertTrue(reader.NextResult(), StoredProcedure.GetProductImageMetadata.CommandName + " returned only 1 of the 4 expected result sets.");

					ReadSizeLocalizations(reader, metadata);
					Safety.AssertTrue(reader.NextResult(), StoredProcedure.GetProductImageMetadata.CommandName + " returned only 2 of the 4 expected result sets.");
		
					ReadViews(reader, metadata);
					Safety.AssertTrue(reader.NextResult(), StoredProcedure.GetProductImageMetadata.CommandName + " returned only 3 of the 4 expected result sets.");

					ReadViewLocalizations(reader, metadata);
					Safety.AssertFalse(reader.NextResult(), StoredProcedure.GetProductImageMetadata.CommandName + " returned more than the 4 expected result sets.");
				}
				finally
				{
					reader.Close();
				}
			}
			return metadata;
		}

		private static void ReadSizes(IDataReader reader, ProductImageMetadata metadata)
		{
			ProductImageSize size;

			Safety.AssertArgumentNotNull("reader", reader);
			Safety.AssertArgumentNotNull("metadata", metadata);

			while (reader.Read())
			{
				size = ReadSize(reader);
				metadata.SizesBySizeId.Add(size.SizeId, size);
                metadata.SizesByWellKnowSizeCode.Add(size.WellKnownSizeCode, size);
			}
		}

		private static void ReadSizeLocalizations(IDataReader reader, ProductImageMetadata metadata)
		{
			ProductImageSizeLocalization sizeLocalization;
			ProductImageSize size;

			Safety.AssertArgumentNotNull("reader", reader);
			Safety.AssertArgumentNotNull("metadata", metadata);

			while (reader.Read())
			{
				sizeLocalization = ReadSizeLocalization(reader);
				size = metadata.SizesBySizeId[sizeLocalization.SizeId];
				size.Localizations.Add(sizeLocalization.CultureId, sizeLocalization);
			}
		}

		private static void ReadViews(IDataReader reader, ProductImageMetadata metadata)
		{
			ProductImageView view;

			Safety.AssertArgumentNotNull("reader", reader);
			Safety.AssertArgumentNotNull("metadata", metadata);

			while (reader.Read())
			{
				view = ReadView(reader);
				metadata.ViewsByViewId.Add(view.ViewId, view);
                metadata.ViewsByWellKnowViewCode.Add(view.WellKnownViewCode, view);
			}
		}

		private static void ReadViewLocalizations(IDataReader reader, ProductImageMetadata metadata)
		{
			ProductImageViewLocalization viewLocalization;
			ProductImageView view;

			Safety.AssertArgumentNotNull("reader", reader);
			Safety.AssertArgumentNotNull("metadata", metadata);

			while (reader.Read())
			{
				viewLocalization = ReadViewLocalization(reader);
				view = metadata.ViewsByViewId[viewLocalization.ViewId];
				view.Localizations.Add(viewLocalization.CultureId, viewLocalization);
			}
		}

		private static ProductImageSize ReadSize(IDataReader reader)
		{
			ProductImageSize size;

			Safety.AssertArgumentNotNull("reader", reader);

			size = new ProductImageSize();
			size.SizeId = (Guid)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageSize.SizeId];
			size.WellKnownSizeCode = (byte)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageSize.WellKnownSizeCode];
			size.Width = (short)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageSize.Width];
			size.Height = (short)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageSize.Height];
			size.DisplayOrder = (int)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageSize.DisplayOrder];
			size.DefaultName = (string)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageSize.DefaultName];
			size.DefaultMissingImagePath = (string)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageSize.DefaultMissingImagePath];
			return size;
		}

		private static ProductImageSizeLocalization ReadSizeLocalization(IDataReader reader)
		{
			ProductImageSizeLocalization localization;

			Safety.AssertArgumentNotNull("reader", reader);

			localization = new ProductImageSizeLocalization();
			localization.SizeId = (Guid)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageSize.SizeId];
			localization.CultureId = (int)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageSizeLocalization.CultureId];
			localization.Name = (string)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageSizeLocalization.Name];
			localization.MissingImagePath = (string)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageSizeLocalization.MissingImagePath];
			return localization;
		}

		private static ProductImageView ReadView(IDataReader reader)
		{
			ProductImageView view;

			Safety.AssertArgumentNotNull("reader", reader);

			view = new ProductImageView();
			view.ViewId = (Guid)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageView.ViewId];
			view.WellKnownViewCode = (byte)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageView.WellKnownViewCode];
			view.DisplayOrder = (int)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageView.DisplayOrder];
			view.DefaultName = (string)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageView.DefaultName];
			return view;
		}

		private static ProductImageViewLocalization ReadViewLocalization(IDataReader reader)
		{
			ProductImageViewLocalization localization;

			Safety.AssertArgumentNotNull("reader", reader);

			localization = new ProductImageViewLocalization();
			localization.ViewId = (Guid)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageView.ViewId];
			localization.CultureId = (int)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageViewLocalization.CultureId];
			localization.Name = (string)reader[StoredProcedure.GetProductImageMetadata.ColumnName.ProductImageViewLocalization.Name];
			return localization;
		}

		#endregion // Implementation

	}

}
